在其他程式語言裡有一個蠻好用的語法,可以創造出有相同邏輯卻能套用在不同型別的函式(function),像是C++的template、C#和Java的Generic等,而TypeScript的類似語法同樣稱為Generic,先舉一個非常簡單的例子來認識TypeScript的Generic。
假設有個函式是可以接收number型別參數並回傳輸入的number型別參數:
function returnNumber(arg: number): number {
return arg;
}
但在另一個情境則是需要類似的函式,只是改成接收string型別參數並回傳輸入的string型別參數:
function returnString(arg: string): string {
return arg;
}
為了確保回傳值型別和參數型別相同,兩個函式都有將回傳值型別明確指定成和輸入參數型別相同的型別。
不過,寫程式有個很基本的原則稱為Dry原則 ─ 「Don't repeat yourself」,意即不要重複你所寫的程式碼。
從這裡可以發現,上述兩個函式雖然名稱和型別不同,但其實函式內部的邏輯都是一樣的 ─ 都只是在回傳相同型別的輸入參數,所以撇開型別來看,我們確實一直在重複相同的程式碼、做同樣的事情。
那有沒有辦法能夠寫出邏輯相同、只是套用在不同型別的函式?
此時可能會想到一種解決方式是 ─ 把型別改成 any
型別來避免重複程式碼的問題:
function returnAny(arg: any): any {
return arg;
}
但是這種寫法會延伸出另一個問題:若函式邏輯愈來愈複雜,any
型別無法保證輸入參數的型別和回傳值的型別是否相同。
因此當我們希望有一種函式是可以用不同型別去執行一樣的事情,這種時候就可以用Generic來徹底解決這個問題。
Generic函式寫法如下:
function returnSomething<T>(arg: T): T {
return arg;
}
範例的 <>
角括號是Generic用來接收型別變數的語法(類似於函式接收引數的 ()
),角括號內的 T
就是用來表示某個未知型別的型別變數,而 T
究竟是何種型別要等到呼叫函式、輸入型別的時候才會知道。
譬如在函式角括號<T>
T 的位置輸入number型別:
returnSomething<number>(1);
/*
* 相當於
* function returnSomething<number>(arg: number): number {
* return arg;
* }
*/
如果需要的是string型別函式就將 <T>
寫成 <string>
:
returnSomething<string>("Hello Generic");
/*
* 相當於
* function returnSomething<string>(arg: string): string {
* return arg;
* }
*/
如此一來,不僅能避免重複邏輯相同的程式碼,同時能保證輸入參數型別和回傳值型別一致,這邊簡單驗證一下:
let stringResult: string;
stringResult = returnSomething<number>(1); // compile error
當然,如果是像上面這種簡單的Generic函式,也能省略指定型別 <T>
的步驟。
如下方例子,TypeScript Compiler 會自行推導(infer)回傳值是string型別:
let stringResult = returnSomething("Hello Generic");
console.log(typeof stringResult); // "string"
此外,在寫函式內部邏輯時,如果已經明確寫出特定型別才有的屬性或方法,必須限制傳入的型別變數至少要有這個屬性或方法,例如以下函式的參數如果只給予型別變數 <T>
,因為TypeScript Compiler無法保證輸入的型別有 length
這個屬性,就會在編譯期間報錯:
function getLength<T>(args: T): number {
return args.length; // compile error
}
所以必須明確給予至少有 length
屬性的generic型別,譬如 generic array:
function getLength<T>(args: T[]): number {
return args.length; // ok
}
或是寫成:
function getLength<T>(args: Array<T>): number {
return args.length; // ok
}
因為是菜鳥系列,不希望篇幅太長,所以今天簡單認識Generic,接下來預計還會有1~2篇繼續了解Generic的其他用法。
參考資料
Generic @TypeScript Handbook
TypeScript